/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "ath.h"
#include "apr_hash.h"

typedef char *(*ath_conf_farm_func) (cmd_parms *cmd, ath_cfg_farm *farm, 
				     const char *args);

typedef struct ath_conf_farm_direc ath_conf_farm_direc;

struct ath_conf_farm_direc {
    const char *name;
    ath_conf_farm_func func;
};

#define ATH_CONF_FARM_DIREC(name, func) { name, func }

const ath_conf_farm_direc * ath_conf_farm_direc_get(const char *name);
char * ath_conf_farm_online(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_member(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_algo(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_hitadds(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_expect(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_maxex(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_downurl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_offurl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_ttl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_rttl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);
char * ath_conf_farm_smartfarmable(cmd_parms *cmd, ath_cfg_farm *farm, const char *args);

/* if the directive has "Ath" in front, it also exists as a
 * server level config option 
 */
static const ath_conf_farm_direc ath_conf_farm_direcs[] =
  {
      ATH_CONF_FARM_DIREC("Online", ath_conf_farm_online),
      ATH_CONF_FARM_DIREC("Member", ath_conf_farm_member),
      ATH_CONF_FARM_DIREC("AthAlgorithm", ath_conf_farm_algo),
      ATH_CONF_FARM_DIREC("Algorithm", ath_conf_farm_algo),
      ATH_CONF_FARM_DIREC("AthAlgoHitAdds", ath_conf_farm_hitadds),
      ATH_CONF_FARM_DIREC("AlgoHitAdds", ath_conf_farm_hitadds),
      ATH_CONF_FARM_DIREC("AthExpectUpdate", ath_conf_farm_expect),
      ATH_CONF_FARM_DIREC("ExpectUpdate", ath_conf_farm_expect),
      ATH_CONF_FARM_DIREC("AthAlgoMaxExcluded", ath_conf_farm_maxex),
      ATH_CONF_FARM_DIREC("AlgoMaxExcluded", ath_conf_farm_maxex),
      ATH_CONF_FARM_DIREC("AthAllDownURL", ath_conf_farm_downurl),
      ATH_CONF_FARM_DIREC("AllDownURL", ath_conf_farm_downurl),
      ATH_CONF_FARM_DIREC("AthOfflineURL", ath_conf_farm_offurl),
      ATH_CONF_FARM_DIREC("OfflineURL", ath_conf_farm_offurl),
      ATH_CONF_FARM_DIREC("AthExpectTTL", ath_conf_farm_ttl),
      ATH_CONF_FARM_DIREC("TTL", ath_conf_farm_ttl),
      ATH_CONF_FARM_DIREC("AthExpectRecoverTTL", ath_conf_farm_rttl),
      ATH_CONF_FARM_DIREC("RTTL", ath_conf_farm_rttl),
      ATH_CONF_FARM_DIREC("SmartFarmable", ath_conf_farm_smartfarmable),
      {NULL},
  };

/* allocates an apache config object */
ath_conf_ap * 
ath_conf_ap_alloc(apr_pool_t *pool)
{
    ath_conf_ap *aconf = (ath_conf_ap *)
      apr_pcalloc(pool, sizeof(ath_conf_ap));
    /* we dont alloc space for locks in order to save start up cost
     * this way we will be able to only alloc the runtime soft max count
     */

    /* This is all formatted with 0's, the default settings follow httpd 
     * module standards in mod_athena.c: 
     *  static void * _athena_conf_ap_init(apr_pool_t *p, server_rec *s)
     */

    ASSERT(aconf);
    aconf->shm_path = NULL;
    aconf->mux_prefix = NULL;
    aconf->mux_count = 0;
    aconf->farm_count = 0;
    aconf->phys_count = 0;
    aconf->diverse_multi = 0;
    aconf->allow_mirror = 0;
    /* we do this because i didn't want pool alloc inside list code */
    aconf->farm_cfgs = (ath_list *) ath_list_ap_alloc(pool);
    aconf->phys_cfgs = (ath_list *) ath_list_ap_alloc(pool);
    aconf->engine_down_url = NULL;
    aconf->engine_down_url_force_get = 0;
    aconf->engine_offline_url = NULL;
    aconf->engine_offline_url_force_get = 0;
    aconf->read_only = (int)NULL;
    aconf->engine_on = 0;
    aconf->engine_default_phys_on = 0;
    aconf->engine_default_farm_on = 0;
    aconf->engine_path = NULL;
    aconf->engine_algo = NULL;
    aconf->engine_expect_update = 0;
    aconf->engine_expect_update_field = ATH_ALGO_NON;
    aconf->engine_hit_adds_to = ATH_ALGO_NON;
    aconf->engine_hit_adds = 0;
    aconf->engine_allow_ttl = 0;
    aconf->engine_recover_ttl = 0;
    aconf->engine_status_farmfmt = NULL;
    aconf->engine_status_physfmt = NULL;
    aconf->engine_status_engifmt = NULL;
    aconf->smartfarm_enable = 0;
    aconf->smartfarm_hashdata = NULL;
    aconf->cookie_name_smartfarm = NULL;
    aconf->smartfarm_tamper_url = NULL;
    return aconf;
}

/* the following functions are all passed to apache config handlers
 * they are self explanatory
 */

const char * 
ath_conf_engine(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_on = flag;
    return NULL;
}


/* Order in conf file matters for this one! must be before farms */

const char * 
ath_conf_farmname_ignorecase(cmd_parms *parms, void *n, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    ath_farm_name_ignorecase(flag);
    return NULL;
}

const char * 
ath_conf_default_phys_on(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_default_phys_on = flag;
    return NULL;
}

const char * 
ath_conf_default_farm_on(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_default_farm_on = flag;
    return NULL;
}

const char * 
ath_conf_mirror(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->allow_mirror = flag;
    return NULL;
}

const char * 
ath_conf_diverse_multi(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->diverse_multi = flag;
    return NULL;
}

/* ath_conf_expectation
 * only applies to global config, farm handled separately
 */
const char * 
ath_conf_expectation(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_expect_update = flag;
    return NULL;
}

const char * 
ath_conf_nomination(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_nominate_per_update = flag;
    return NULL;
}

const char * 
ath_conf_max_excluded(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_max_excluded = flag;
    return NULL;
}

const char * 
ath_conf_path(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    int len;
    ASSERT(aconf);

    len = strlen(arg);
    if (*(arg + len - 1) == '/')
	aconf->engine_path = arg;
    else
	aconf->engine_path = apr_pstrcat(parms->pool, arg, "/", NULL);
    return NULL;
}

const char * 
ath_conf_downurl(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_down_url = arg;
    return NULL;
}

const char *  
ath_conf_downurl_forceget(cmd_parms *parms, void *dummy, int flag) 
{ 
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server); 
    ASSERT(aconf); 
    aconf->engine_down_url_force_get = flag; 
    return NULL; 
} 

const char * 
ath_conf_offlineurl(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_offline_url = arg;
    return NULL;
}

const char *  
ath_conf_offlineurl_forceget(cmd_parms *parms, void *dummy, int flag) 
{ 
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server); 
    ASSERT(aconf); 
    aconf->engine_offline_url_force_get = flag; 
    return NULL; 
} 

const char * 
ath_conf_shmpath(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->shm_path = ap_server_root_relative(parms->pool, arg);
    return NULL;
}

const char * 
ath_conf_muxpath(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->mux_prefix = ap_server_root_relative(parms->pool, arg);
    return NULL;
}

const char * 
ath_conf_muxcount(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    if ((!strncmp(arg, "-1", 2)) || (!strncmp(arg, "row", 3)))
	aconf->mux_count = ATH_EOA;
    else
      {
	  aconf->mux_count = atoi(arg);
	  if ((aconf->mux_count < 3) 
	      || (aconf->mux_count > ATH_MUXS_HARDMAX))
	      return (apr_psprintf(parms->pool,
				   "Bad AthMuxCount: %d > %d compiled max or < 3.",
				   aconf->mux_count, ATH_MUXS_HARDMAX));
      }
    return NULL;
}

const char * 
ath_conf_expects_field(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    aconf->engine_expect_update_field = ath_algo_str_to_type(arg);
    ASSERT(aconf);
    return NULL;
}

const char * 
ath_conf_readonly(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    int c = *arg;
    ASSERT(aconf);
    if (!strncmp(arg, "none", 4))
	aconf->read_only = ATH_READ_WRITE;
    else if (!strncmp(arg, "strict", 6))
	aconf->read_only = ATH_READ_ONLY;
    else
	return (apr_psprintf(parms->pool,
			     "Bad AthReadOnlyPolicy: %s must be {none|strict}.",
			     arg));
    return NULL;
}

const char * 
ath_conf_farmfmt(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_status_farmfmt = arg;
    return NULL;
}

const char * 
ath_conf_physfmt(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_status_physfmt = arg;
    return NULL;
}

const char * 
ath_conf_engifmt(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_status_engifmt = arg;
    return NULL;
}

const char * 
ath_conf_ttl(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    int ttl = atoi(arg);
    ASSERT(aconf);
    aconf->engine_allow_ttl = ttl;
    return NULL;
}

const char * 
ath_conf_recover_ttl(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    int ttl = atoi(arg);
    ASSERT(aconf);
    aconf->engine_recover_ttl = ttl;
    return NULL;
}

/* ath_conf_algorithm
 * only applies to global config, farm handled separately
 */
const char * 
ath_conf_algorithm(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_algo = arg;
    return NULL;
}

const char * 
ath_conf_hitadds(cmd_parms *parms, void *dummy, const char *arg1, const char *arg2)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->engine_hit_adds_to = *arg1;
    aconf->engine_hit_adds = atof(arg2);
    return NULL;
}

const char *
ath_conf_cookie_name_smartfarm(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->cookie_name_smartfarm = arg;
    return NULL;
}

const char *
ath_conf_smartfarm_protect(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->smartfarm_hashdata = arg;
    return NULL;
}

const char *
ath_conf_smartfarm_tamper_url(cmd_parms *parms, void *dummy, const char *arg)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->smartfarm_tamper_url = arg;
    return NULL;
}

const char * 
ath_conf_smartfarm_enable(cmd_parms *parms, void *dummy, int flag)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(parms->server);
    ASSERT(aconf);
    aconf->smartfarm_enable = flag;
    return NULL;
}


/* end apache config handlers */

/* special ath config called during AthFarm config handler 
 * uses directory nesting logic but not in the context config
 * concept that apache expects, so we have reproduce some of
 * our other config handler logic
 */
const char * 
ath_conf_farm(cmd_parms *cmd, void *dummy, const char *arg)
{
    int i;
    ath_cfg_farm *farm;
    ap_directive_t *direc_nested;
    const char *args;
    char *msg = NULL;
    char *str_return = NULL;
    ath_conf_ap *aconf = ATH_CONF_AP_GET(cmd->server);
    ASSERT(aconf);

    /* add farm */
    farm = ath_farm_cfg_add(cmd->pool, aconf->farm_cfgs, arg);
    if (farm == NULL)
      {
	  msg = "<AthFarm > must be at least 1 char, contain no ':'"
	    ", and cannot be multiply declared (case doesn't count by default)";
	  return msg;
      }
    /* get first nested directive */
    direc_nested = cmd->directive->first_child;
    /* now parse possible directives 
     * the include: Algorithm, AlgoHitAdds, ExpectUpdate, Member
     * if any of the first three are not specified, the engine will
     * use the engine wide settings (speficied with the same directives
     * outside the AthFarm block) for the farm
     *
     * nested is usually used for per-directory contained directives
     * but this is totally different so we handle.
     */
    while (direc_nested != NULL)
      {
	  if (direc_nested->directive)
	    {
		const ath_conf_farm_direc *direc;
		direc = ath_conf_farm_direc_get(direc_nested->directive);
		if (direc)
		    str_return = (direc->func)(cmd, farm, direc_nested->args);
		else
		  {
		      str_return = 
			apr_pstrcat(cmd->pool, "<AthFarm ", farm->name, 
				    "> contains unknown directive: ", 
				    direc_nested->directive,
				    NULL);
		  }
	    }
	  if (str_return != NULL)
	      return str_return;
	  direc_nested = direc_nested->next;
      }
    return NULL;
}

const ath_conf_farm_direc *
ath_conf_farm_direc_get(const char *name)
{
    int i = 0;
    const ath_conf_farm_direc *direc;
    direc = &ath_conf_farm_direcs[i];
    while (direc->name != NULL)
      {
	  if (!strcasecmp(direc->name, name))
	      return direc;
	  i++;
	  direc = &ath_conf_farm_direcs[i];
      }
    return NULL;
}

char *
ath_conf_farm_online(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (args)
	farm->on = ath_flag_is_on(args);
    return NULL;
}

char * 
ath_conf_farm_member(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    char *str_return = NULL;
    ath_cfg_phys *member = NULL;
    ath_conf_ap *aconf = ATH_CONF_AP_GET(cmd->server);
    member = 
      ath_farm_cfg_add_member(cmd->pool, farm, aconf->phys_cfgs, args);
    if (!member)
      {
	  str_return =
	    apr_pstrcat(cmd->pool, "<AthFarm ", farm->name,
			"> Member must include a hostname or IP",
			NULL);
      }
    return str_return;
}

char * 
ath_conf_farm_algo(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    char *myargs;
    myargs = apr_pstrndup(cmd->pool, args, strlen(args));
    farm->algo_str = myargs;
    return NULL;
}

char * 
ath_conf_farm_hitadds(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (args != NULL)
      {
	  int c = *args;
	  farm->hit_adds_to = (char) c;
	  /* advance to white space */
	  while ((*args != (int) NULL) && (*args != ' '))
	      args++;
	  /* advance to value */
	  while ((*args != (int) NULL)
		 && ((*args == ' ') || (*args == '\t')))
	      args++;
	  if ((*args == (int) NULL) && (*(args + 1) != (int) NULL))
	      args++;
	  if (args != NULL)
	      farm->hit_adds = atof(args);
      }
    return NULL;
}

char * 
ath_conf_farm_expect(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (ath_flag_is_on(args))
	farm->expect_update = 1;
    else
	farm->expect_update = 0;
    return NULL;
}

char * 
ath_conf_farm_maxex(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (ath_flag_is_on(args))
	farm->max_excluded = 1;
    else
	farm->max_excluded = 0;
    return NULL;
}

char * 
ath_conf_farm_downurl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    char *myargs;
    if (args != NULL)
      {
	  myargs = apr_pstrndup(cmd->pool, args, strlen(args));
	  farm->down_url = myargs;
      }
    return NULL;
}

char * 
ath_conf_farm_offurl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    char *myargs;
    if (args != NULL)
      {
          myargs = apr_pstrndup(cmd->pool, args, strlen(args));
          farm->offline_url = myargs;
      }
    return NULL;

}

char * 
ath_conf_farm_ttl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (args != NULL)
	farm->allow_ttl = atoi(args);
    return NULL;
}

char * 
ath_conf_farm_rttl(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (args != NULL)
	farm->recover_ttl = atoi(args);
    return NULL;
}

char *
ath_conf_farm_smartfarmable(cmd_parms *cmd, ath_cfg_farm *farm, const char *args)
{
    if (ath_flag_is_on(args))
	farm->smartfarmable = 1;
    else
	farm->smartfarmable = 0;
    return NULL;
}
